package semant.secondpass;

import semant.Env;
import semant.TypeChecker;
import symbol.ClassInfo;
import symbol.MethodInfo;
import symbol.VarInfo;
import syntaxtree.ArrayAssign;
import syntaxtree.ArrayLength;
import syntaxtree.Assign;
import syntaxtree.Block;
import syntaxtree.BooleanType;
import syntaxtree.If;
import syntaxtree.IntArrayType;
import syntaxtree.IntegerType;
import syntaxtree.Print;
import syntaxtree.Type;
import syntaxtree.VisitorAdapter;
import syntaxtree.Statement;
import syntaxtree.While;
import util.List;

public class StatementHandler extends VisitorAdapter
{
	private Env e;
	private ClassInfo ci;
	private MethodInfo mi;
	
	private StatementHandler(Env e, ClassInfo ci, MethodInfo mi)
	{
		this.e = e;
		this.ci = ci;
		this.mi = mi;
	}
	
	public static void secondPass(Env e, ClassInfo ci, MethodInfo mi, List<Statement> stmList) 
	{			
		for (List<Statement> l = stmList; l != null; l = l.tail)
			StatementHandler.secondPass(e, ci, mi, l.head);	
	}
	
	public static void secondPass(Env e, ClassInfo ci, MethodInfo mi, Statement node) 
	{
		StatementHandler h = new StatementHandler(e, ci, mi);
				
		node.accept(h);
	}
	
	@Override
	public void visit(ArrayAssign node)
	{
		// id[index] = value
		
		Type indexType = ExpHandler.secondPass(e, ci, mi, node.index);
		Type valueType = ExpHandler.secondPass(e, ci, mi, node.value);
		VarInfo id = TypeChecker.getVariable(ci, mi, node.var.s);
		
		if (!(indexType instanceof IntegerType))
		{
			e.err.Error(node, new Object[]{"Tipo inválido para índice de vetor.",
					"Esperado: int",
					"Encontrado: " + indexType.toString()});
		}
		
		if (!(valueType instanceof IntegerType))
		{
			e.err.Error(node, new Object[]{"Tipo inválido para atribuição a vetor.",
					"Esperado: int",
					"Encontrado: " + valueType.toString()});
		}
		
		if (id == null)
		{
			TypeChecker.erroIdentificadorNaoDeclarado(e, node.var);
		}
		else if (!(id.type instanceof IntArrayType))
		{
			e.err.Error(node, new Object[]{"Tipo inválido para atribuição de vetor.",
					"Esperado: vetor de inteiro",
					"Encontrado: " + id.type.toString()});
		}
		
	}
	
	@Override
	public void visit(ArrayLength node)
	{
		// a.length
		
		Type arrayType = ExpHandler.secondPass(e, ci, mi,node.array);
		
		if (!(arrayType instanceof IntArrayType))
		{
			e.err.Error(node, new Object[]{"Tipo inválido para propriedade 'length'.",
					"Esperado: vetor de inteiro",
					"Encontrado: " + arrayType.toString()});
		}
	}
	
	@Override
	public void visit(Assign node)
	{
		// a = b
		
		Type valueType = ExpHandler.secondPass(e, ci, mi, node.exp);
		VarInfo id = TypeChecker.getVariable(ci, mi, node.var.s);
		
		if (id == null)
			TypeChecker.erroIdentificadorNaoDeclarado(e, node.var);
		else if (!TypeChecker.compareType(id.type, valueType, e))
		{
			e.err.Error(node, new Object[]{"Atribuição de tipos diferentes.",
					"Tentativa de atribuir '"+valueType.toString()+"' a '"+ id.type.toString() +"'"});
		}
	}
	
	@Override
	public void visit(Block node)
	{
		StatementHandler.secondPass(e, ci, mi, node.body);
	}
	
	@Override
	public void visit(If node)
	{
		Type conditionType = ExpHandler.secondPass(e, ci, mi, node.condition);
		
		if (!(conditionType instanceof BooleanType))
		{
			e.err.Error(node, new Object[]{"Tipo inválido para expressão de teste.",
					"Esperado: boolean",
					"Encontrado: " + conditionType.toString()});
		}
		
		StatementHandler.secondPass(e, ci, mi, node.thenClause);
		
		if (node.elseClause != null)
			StatementHandler.secondPass(e, ci, mi, node.elseClause);
	}
	
	@Override
	public void visit(Print node)
	{
		Type t = ExpHandler.secondPass(e, ci, mi, node.exp);
		
		if (!(t instanceof IntegerType))
		{
			e.err.Error(node, new Object[]{"Tipo inválido para impressão.",
					"Esperado: int",
					"Encontrado: " + t.toString()});
		}
	}
	
	@Override
	public void visit(While node)
	{
		Type conditionType = ExpHandler.secondPass(e, ci, mi, node.condition);
		
		if (!(conditionType instanceof BooleanType))
		{
			e.err.Error(node, new Object[]{"Tipo inválido para expressão de teste.",
					"Esperado: boolean",
					"Encontrado: " + conditionType.toString()});
		}
		
		StatementHandler.secondPass(e, ci, mi, node.body);
	}
}
